package com.billy.ohos.swipe;

import com.billy.ohos.swipe.listener.SimpleSwipeListener;
import com.billy.ohos.swipe.consumer.*;
import ohos.aafwk.ability.Ability;
import ohos.aafwk.ability.AbilityLifecycleCallbacks;
import ohos.aafwk.ability.AbilityPackage;
import ohos.agp.components.Component;
import ohos.agp.utils.Color;
import ohos.utils.PacMap;

import java.util.ArrayList;

import static com.billy.ohos.swipe.SwipeConsumer.*;

/**
 * A tool for achieving a one-line global ability swipe back via using some {@link SwipeConsumer}
 * simple usage:
 * <code>
 * <p>
 * //add swipe translucent back performance for all abilities
 * // (default direction: left, previous ability related factor:0.5F)
 * SmartSwipeBack.abilitySlidingBack(this, new SmartSwipeBack.AbilitySwipeBackFilter() {
 * public boolean onFilter(Ability ability) {
 * return !(ability instanceof MainAbility);
 * }
 * });
 * <p>
 * //add swipe back like mobile QQ (ability keep stay and finish ability with release velocity)
 * //SmartSwipeBack.abilityStayBack(this, null);
 * <p>
 * //add bezier swipe back like XiaoMi (swipe with bezier back consumer at edge of screen)
 * //SmartSwipeBack.abilityBezierBack(this, null);
 * </code>
 *
 * @author billy.qi
 */
public class SmartSwipeBack {
    public static final ArrayList<Ability> ABILITIES = new ArrayList<>();
    private static IPreviousFinder mPreviousFinder;

    public interface SwipeBackConsumerFactory {
        /**
         * Create SwipeConsumer to do swipe back business for ability
         *
         * @param ability ability to wrap with swipe back
         * @return SwipeConsumer
         */
        SwipeConsumer createSwipeBackConsumer(Ability ability);
    }

    public interface AbilitySwipeBackFilter {
        /**
         * Determine whether the ability parameter should swipe back
         *
         * @param ability The ability to wrap or not
         * @return true: need to wrap with swipe back, false: do not wrap
         */
        boolean onFilter(Ability ability);
    }

    public static void abilityBack(AbilityPackage application, SwipeBackConsumerFactory factory) {
        abilityBack(application, factory, null);
    }


    private static NewElementsCallback mElementsCallback;


    /**
     * The core function for global ability swipe back
     *
     * @param application application
     * @param factory     factory to create SwipeConsumer for each Ability
     * @param filter      filter of ability, to determine which ability should finish via swipe motion
     */
    public static void abilityBack(AbilityPackage application, SwipeBackConsumerFactory factory, AbilitySwipeBackFilter filter) {
        if (mElementsCallback == null) {
            mElementsCallback = new NewElementsCallback();
        }
        if (abilitySwipeBackListener == null) {
            abilitySwipeBackListener = new AbilitySwipeBackListener(factory, filter);
        } else {
            application.unregisterCallbacks(abilitySwipeBackListener, mElementsCallback);
            abilitySwipeBackListener.mFactory = factory;
            abilitySwipeBackListener.mFilter = filter;
        }
        application.registerCallbacks(abilitySwipeBackListener, mElementsCallback);
    }

    ////////////////////////////////////////////
    //
    //  swipe back with StayConsumer
    //
    ////////////////////////////////////////////

    public static void abilityStayBack(AbilityPackage application, AbilitySwipeBackFilter filter) {
        final int edgeSize = SmartSwipe.dp2px(20, application);
        abilityStayBack(application, filter, edgeSize, 0, DIRECTION_LEFT);
    }

    public static void abilityStayBack(AbilityPackage application, AbilitySwipeBackFilter filter, final int edgeSize, final int minVelocity, final int direction) {
        SmartSwipeBack.abilityBack(application, new SwipeBackConsumerFactory() {
            @Override
            public SwipeConsumer createSwipeBackConsumer(Ability ability) {
                return new StayConsumer()
                        .setMinVelocity(minVelocity)
                        .setEdgeSize(edgeSize)
                        .enableDirection(direction)
                        .addListener(new SimpleSwipeListener() {
                            @Override
                            public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
                                if (ability != null) {
                                    ability.terminateAbility();
                                }
                            }
                        });
            }
        }, filter);
    }

    ////////////////////////////////////////////
    //
    //  swipe back with AbilitySlidingBackConsumer
    //
    ////////////////////////////////////////////

    public static void abilitySlidingBack(AbilityPackage application, AbilitySwipeBackFilter filter) {
        final float factor = 0.5f;
        // with default scrimColor: transparent
        abilitySlidingBack(application, filter, factor);
    }

    public static void abilitySlidingBack(AbilityPackage application, AbilitySwipeBackFilter filter, float factor) {
        //default edge size
        final int edgeSize = SmartSwipe.dp2px(20, application);
        final int shadowColor = 0x80000000;
        final int shadowSize = SmartSwipe.dp2px(10, application);
        final int direction = DIRECTION_LEFT;
        abilitySlidingBack(application, filter, edgeSize, Color.TRANSPARENT.getValue(), shadowColor, shadowSize, factor, direction);
    }

    public static void abilitySlidingBack(AbilityPackage application, AbilitySwipeBackFilter filter,
                                          final int edgeSize, final int scrimColor, final int shadowColor, final int shadowSize,
                                          final float factor, final int direction) {
//        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
//            //if sdk version is less than 21, the compatibility of AbilitySlidingBackConsumer is not good enough
//            //use StayConsumer instead for ohos sdk version <= 20
//            abilityStayBack(application, filter, edgeSize, 0, direction);
//        } else {
        abilityBack(application, new SwipeBackConsumerFactory() {
            @Override
            public SwipeConsumer createSwipeBackConsumer(Ability ability) {
                return new AbilitySlidingBackConsumer(ability)
                        .setRelativeMoveFactor(factor)
                        .setScrimColor(scrimColor)
                        .setShadowColor(shadowColor)
                        .setShadowSize(shadowSize)
                        .setEdgeSize(edgeSize)
                        .enableDirection(direction)
                        .addListener(new SimpleSwipeListener() {
                            @Override
                            public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
                                if (ability != null) {
                                    ability.terminateAbility();
                                    ability.setTransitionAnimation(ResourceTable.Animation_anim_none, ResourceTable.Animation_anim_none);
                                }
                            }
                        });
            }
        }, filter);
    }
//    }

    ////////////////////////////////////////
    //
    //  swipe back with BezierBackConsumer
    //
    ////////////////////////////////////////

    public static void abilityBezierBack(AbilityPackage application, AbilitySwipeBackFilter filter) {
        final int edgeSize = SmartSwipe.dp2px(20, application);
        abilityBezierBack(application, filter, edgeSize);
    }

    public static void abilityBezierBack(AbilityPackage application, AbilitySwipeBackFilter filter, int edgeSize) {
        final int thickness = SmartSwipe.dp2px(30, application);
        final int size = SmartSwipe.dp2px(200, application);
        final int direction = DIRECTION_LEFT;
        abilityBezierBack(application, filter, edgeSize, size, thickness, Color.BLACK.getValue(), Color.WHITE.getValue(), direction);
    }

    public static void abilityBezierBack(AbilityPackage application, AbilitySwipeBackFilter filter,
                                         final int edgeSize, final int size, final int thickness,
                                         final int color, final int arrowColor, final int direction) {
        SmartSwipeBack.abilityBack(application, new SwipeBackConsumerFactory() {
            @Override
            public SwipeConsumer createSwipeBackConsumer(final Ability ability) {
                return new BezierBackConsumer()
                        .setColor(color)
                        .setArrowColor(arrowColor)
                        .setSize(size)
                        .setOpenDistance(thickness)
                        .addListener(new SimpleSwipeListener() {
                            @Override
                            public void onSwipeRelease(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction, float progress, float xVelocity, float yVelocity) {
                                if (progress >= 1) {
                                    ability.terminateAbility();
                                }
                            }
                        })
                        .setEdgeSize(edgeSize)
                        .enableDirection(direction);
            }
        }, filter);
    }

    ////////////////////////////////////////
    //
    //  swipe back with AbilityDoorBackConsumer
    //
    ////////////////////////////////////////

    public static void abilityDoorBack(AbilityPackage application, AbilitySwipeBackFilter filter) {
        final int edgeSize = SmartSwipe.dp2px(20, application);
        final int scrimColor = 0x80000000;
        final boolean refreshable = true;
        abilityDoorBack(application, filter, DIRECTION_LEFT, edgeSize, scrimColor, refreshable);
    }

    public static void abilityDoorBack(AbilityPackage application, AbilitySwipeBackFilter filter,
                                       final int direction, final int edgeSize, final int scrimColor, final boolean refreshable) {
        SmartSwipeBack.abilityBack(application, new SwipeBackConsumerFactory() {
            @Override
            public SwipeConsumer createSwipeBackConsumer(final Ability ability) {
                return new AbilityDoorBackConsumer(ability)
                        .setRefreshable(refreshable)
                        .setScrimColor(scrimColor)
                        .enableDirection(direction)
                        .setEdgeSize(edgeSize)
                        .addListener(new SimpleSwipeListener() {
                            @Override
                            public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
                                ability.terminateAbility();
                                ability.setTransitionAnimation(ResourceTable.Animation_anim_none, ResourceTable.Animation_anim_none);
                            }
                        });
            }
        }, filter);
    }

    ////////////////////////////////////////
    //
    //  swipe back with AbilityShuttersBackConsumer
    //
    ////////////////////////////////////////

    public static void abilityShuttersBack(AbilityPackage application, AbilitySwipeBackFilter filter) {
        final int edgeSize = SmartSwipe.dp2px(20, application);
        final int scrimColor = 0x80000000;
        final boolean refreshable = true;
        abilityShuttersBack(application, filter, DIRECTION_LEFT, edgeSize, scrimColor, refreshable);
    }

    public static void abilityShuttersBack(AbilityPackage application, AbilitySwipeBackFilter filter,
                                           final int direction, final int edgeSize, final int scrimColor, final boolean refreshable) {
        SmartSwipeBack.abilityBack(application, new SwipeBackConsumerFactory() {
            @Override
            public SwipeConsumer createSwipeBackConsumer(final Ability ability) {
                return new AbilityShuttersBackConsumer(ability)
                        .setRefreshable(refreshable)
                        .setScrimColor(scrimColor)
                        .enableDirection(direction)
                        .setEdgeSize(edgeSize)
                        .addListener(new SimpleSwipeListener() {
                            @Override
                            public void onSwipeOpened(SmartSwipeWrapper wrapper, SwipeConsumer consumer, int direction) {
                                ability.terminateAbility();
                                ability.setTransitionAnimation(ResourceTable.Animation_anim_none, ResourceTable.Animation_anim_none);
                            }
                        });
            }
        }, filter);
    }


    private static AbilitySwipeBackListener abilitySwipeBackListener;

    public static class AbilitySwipeBackListener implements AbilityLifecycleCallbacks {
        private SmartSwipeBack.SwipeBackConsumerFactory mFactory;
        private SmartSwipeBack.AbilitySwipeBackFilter mFilter;

        AbilitySwipeBackListener(SmartSwipeBack.SwipeBackConsumerFactory factory, SmartSwipeBack.AbilitySwipeBackFilter filter) {
            this.mFactory = factory;
            this.mFilter = filter;
        }

        @Override
        public void onAbilityStart(Ability ability) {
            ABILITIES.add(ability);
            if (mFactory == null) {
                return;
            }
            if (mFilter != null && !mFilter.onFilter(ability)) {
                return;
            }
            Component content = ability.findComponentById(ConstantId.ABLITY_CONTENT_ID);
            if (content != null){
                SmartSwipe.wrap(content).addConsumer(mFactory.createSwipeBackConsumer(ability));
            }
        }

        @Override
        public void onAbilityActive(Ability ability) {

        }

        @Override
        public void onAbilityInactive(Ability ability) {

        }

        @Override
        public void onAbilityForeground(Ability ability) {

        }

        @Override
        public void onAbilityBackground(Ability ability) {

        }

        @Override
        public void onAbilityStop(Ability ability) {
            ABILITIES.remove(ability);
        }

        @Override
        public void onAbilitySaveState(PacMap pacMap) {

        }
    }

    /**
     * find previous ability
     *
     * @param fromAbility the given ability to find its previous
     * @return the previous ability if exists
     * @see AbilitySlidingBackConsumer  Ability
     */
    public static Ability findPreviousAbility(Ability fromAbility) {
        if (mPreviousFinder != null) {
            return mPreviousFinder.findPreviousAbility(fromAbility);
        }
        if (fromAbility != null) {
            int index = ABILITIES.indexOf(fromAbility);
            if (index > 0) {
                return ABILITIES.get(index - 1);
            }
        }
        return null;
    }

    public static void setPreviousFinder(IPreviousFinder previousFinder) {
        mPreviousFinder = previousFinder;
    }

    public static interface IPreviousFinder {
        /**
         * find the previous ability for the given ability
         *
         * @param fromAbility ability given
         * @return the previous ability
         */
        Ability findPreviousAbility(Ability fromAbility);
    }

}
